﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;

using BitmapTagger;

using ImageTagEditor.Properties;
using ImageTagEditor.ValueConverters;

using Microsoft.Win32;

using ShaderEffectLibrary;
using System.Windows.Markup;
using System.Xml;



namespace ImageTagEditor
{
	/// <summary>
	/// Interaction logic for MainWindow.xaml
	/// </summary>
	public partial class MainWindow : Window
	{

		/// <summary>
		/// The effect used to visual show that a drag/drop is valid
		/// </summary>
		private ColorToneEffect dragEffect = new ColorToneEffect();


		/// <summary>
		/// The collection of opened images
		/// </summary>
		private ObservableCollection<ObservableBitmapTagger> bitmapTaggers;

		/// <summary>
		/// Webite address for issues
		/// </summary>
		private const string ISSUE_SITE_ADDRESS = @"http://bitmaptagger.codeplex.com/WorkItem/List.aspx";


		/// <summary>
		/// Used to tell when the application is still starting up
		/// </summary>
		private bool startingApplication;

		public MainWindow()
		{
			startingApplication = true;
			InitializeComponent();
			dragEffect.LightColor = Colors.LightBlue;
			dragEffect.DarkColor = Colors.DarkBlue;

			//imageViews = new ObservableCollection<ImageTagView>();
			bitmapTaggers = new ObservableCollection<ObservableBitmapTagger>();

			Binding bind = new Binding();
			bind.Source = this;
			bind.Path = new PropertyPath(SelectedIndexProperty);
			bind.Mode = BindingMode.OneWay;
			bind.Converter = new ZeroToOneIndexBaseValueConverter();
			currentIndexTextBlock.SetBinding(TextBlock.TextProperty, bind);

			bind = new Binding();
			bind.Source = bitmapTaggers;
			bind.Path = new PropertyPath("Count");
			bind.Mode = BindingMode.OneWay;
			imageCountTextBlock.SetBinding(TextBlock.TextProperty, bind);

			LoadSettings();
			ProcessCommandLineArguments();
			SetBackButtonVisibility();
			SetForwardButtonVisibility();
			SetImageCollectionControlGridVisibility();



			startingApplication = false;
			ShowSelectedBitmapTagger();
			if (HasSelectedBitmapTagger)
				imageTagView.ShowImage();

		}

		/// <summary>
		/// Processes any commandline arguments 
		/// </summary>
		/// <param name="args"></param>
		private void ProcessCommandLineArguments()
		{
			string[] args = Environment.GetCommandLineArgs();

			if (args != null)
			{
				foreach (string arg in args)
				{
					if (System.IO.File.Exists(arg))
					{
						string extension = System.IO.Path.GetExtension(arg);
						if(ExtensionChecker.IsPNGExtension(extension) ||
							ExtensionChecker.IsJPEGExtension(extension))
							OpenImage(arg, true);
					}
					else if (IsDirectory(arg))
						OpenDirectoryOfImages(arg);
				}
			}
		}


		#region selected index and image

		/// <summary>
		/// Get the selected bitmap tager
		/// </summary>
		/// <remarks>
		/// Use SelectedIndex to set 
		/// </remarks>
		public ObservableBitmapTagger SelectedBitmapTagger
		{
			get
			{
				return bitmapTaggers[SelectedIndex];
			}
		}

		/// <summary>
		/// Get whether or not there is a selected bitmap tagger
		/// </summary>
		public bool HasSelectedBitmapTagger
		{
			get { return bitmapTaggers.Count != 0; }
		}

		/// <summary>
		/// Get whether the selected image tag view is the first in the 
		/// open image collection
		/// </summary>
		public bool IsSelectedImageTagViewFirstInCollection
		{
			get { return SelectedIndex == 0 && HasSelectedBitmapTagger;}
		}

		/// <summary>
		/// Get whether the selected image tag view is the last in the 
		/// open image collection
		/// </summary>
		public bool IsSelectedImageTagViewLastInCollection
		{
			get { return HasSelectedBitmapTagger && SelectedIndex == LargestIndex; }
		}

		/// <summary>
		/// Get whether the open image collection has mutiple images open
		/// </summary>
		public bool DoesOpenImageCollectionHaveMultipleImages
		{
			get { return bitmapTaggers.Count > 1; }
		}


		/// <summary>
		/// Get or set the selected index
		/// </summary>
		public int SelectedIndex
		{
			get { return (int)GetValue(SelectedIndexProperty); }
			set { SetValue(SelectedIndexProperty, value); }
		}

		/// <summary>
		/// Get the largest available index that is selectable
		/// </summary>
		public int LargestIndex
		{
			get { return bitmapTaggers.Count - 1; }
		}

		/// <summary>
		/// Identifies the SelectedIndex dependency propert
		/// </summary>
		public static readonly DependencyProperty SelectedIndexProperty =
			DependencyProperty.Register("SelectedIndex", typeof(int), typeof(MainWindow), new UIPropertyMetadata(0, OnSelectedIndexChanged));

		/// <summary>
		/// Dependency Property SelectedINdex changed listener
		/// </summary>
		/// <param name="sender">The MainWindow whose dependency property changed</param>
		/// <param name="e">The event args that came with the change</param>
		protected static void OnSelectedIndexChanged(object sender, DependencyPropertyChangedEventArgs e)
		{
			(sender as MainWindow).OnSelectedIndexChanged(e);
		}

		/// <summary>
		/// Listens for when the SelectedIndex Dependency Property value changes.
		/// </summary>
		/// <param name="e">Not Used</param>
		protected void OnSelectedIndexChanged(DependencyPropertyChangedEventArgs e)
		{

			//Add the new one
			ShowSelectedBitmapTagger();

			SetBackButtonVisibility();
			SetForwardButtonVisibility();
			SetImageCollectionControlGridVisibility();
		}

		/// <summary>
		/// Sets the back button's visibility based of the current index
		/// and the image collection count
		/// </summary>
		private void SetBackButtonVisibility()
		{
			if (!DoesOpenImageCollectionHaveMultipleImages || 
				IsSelectedImageTagViewFirstInCollection)
				backButton.Visibility = Visibility.Hidden;
			else
				backButton.Visibility = Visibility.Visible;
		}

		/// <summary>
		/// Sets the forward button's visiblity based on the current index
		/// and the image collection count
		/// </summary>
		private void SetForwardButtonVisibility()
		{
			if (!DoesOpenImageCollectionHaveMultipleImages ||
				IsSelectedImageTagViewLastInCollection)
				forwardButton.Visibility = Visibility.Hidden;
			else
				forwardButton.Visibility = Visibility.Visible;
		}

		/// <summary>
		/// Sets the visibility of the image collection control grid
		/// based on the image collection count
		/// </summary>
		private void SetImageCollectionControlGridVisibility()
		{
			if (HasSelectedBitmapTagger)
				imageCollectionControlGrid.Visibility = Visibility.Visible;
			else
				imageCollectionControlGrid.Visibility = Visibility.Hidden;
		}

		/// <summary>
		/// Shows the SelectedBitmapTagger if there is one to show
		/// </summary>
		/// <remarks>
		/// Make sure the are not image tag views currently being shown
		/// </remarks>
		private void ShowSelectedBitmapTagger()
		{
			if (HasSelectedBitmapTagger)
			{
				imageTagView.Tagger = SelectedBitmapTagger;
				imageTagView.Visibility = Visibility.Visible;
			}
			else
				imageTagView.Visibility = Visibility.Hidden;
		}

		#endregion


		#region glass extension

		/// <summary>
		/// Change source initialization to add in glass extension to window
		/// </summary>
		/// <param name="e">The event argument of the initialization</param>
		protected override void OnSourceInitialized(EventArgs e)
		{
			base.OnSourceInitialized(e);

			if (Environment.OSVersion.Version.Build > VISTA_BUILD)
			{

				GlassHelper.ExtendGlassFrame(this, new Thickness(-1, -1, -1, -1));

				//Attach a window procedure in order to detect later enabling of desktop
				//Composition
				IntPtr hwnd = new WindowInteropHelper(this).Handle;
				HwndSource.FromHwnd(hwnd).AddHook(new HwndSourceHook(WndProc));
			}
		}

		/// <summary>
		/// Listens for when the DWM composition changes and extends the glass
		/// again if necessary
		/// </summary>
		/// <param name="hwnd">Needed for Win32</param>
		/// <param name="msg">Needed for Win32</param>
		/// <param name="wParam">Needed for Win32</param>
		/// <param name="IParam"><Needed for Win32/param>
		/// <param name="handled">Needed for Win32</param>
		/// <returns>Needed for Win32</returns>
		private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr IParam, ref bool handled)
		{
			if (msg == WM_DWM_COMPOSITION_CHANGED)
			{
				//Reenable glass
				GlassHelper.ExtendGlassFrame(this, new Thickness(0, 0, 0, 30));
				handled = true;
			}
			return IntPtr.Zero;
		}

		/// <summary>
		/// Message for from Win32 when the DWM composition is changed
		/// </summary>
		private const int WM_DWM_COMPOSITION_CHANGED = 0x031E;

		/// <summary>
		/// Identifier for the first vista build
		/// </summary>
		private const int VISTA_BUILD = 6000;

		#endregion

		#region save 

		/// <summary>
		/// Listens for when the save button is clicked
		/// </summary>
		/// <param name="sender">Not Used</param>
		/// <param name="e">Not Used</param>
		private void saveButton_Click(object sender, RoutedEventArgs e)
		{
			if (Keyboard.Modifiers == ModifierKeys.Control)
			{
				SaveAsSelectedImage();
			}
			else
			{
				SaveSelectedImage();
			}
		}

		/// <summary>
		/// Listens for when saveImageAsMenuItem is clicked and
		/// saves the selected image
		/// </summary>
		/// <param name="sender">Not Used</param>
		/// <param name="e">Not Used</param>
		private void saveImageAsMenuItem_Click(object sender, RoutedEventArgs e)
		{
			SaveAsSelectedImage();
		}

		/// <summary>
		/// Listens for when saveImageMenuItem is clicked and 
		/// saves the selected image as
		/// </summary>
		/// <param name="sender">Not Used</param>
		/// <param name="e">Not Used</param>
		private void saveImageMenuItem_Click(object sender, RoutedEventArgs e)
		{
			SaveSelectedImage();
		}

		/// <summary>
		/// Listens for when saveAllImageMenuItem is clicked
		/// and saves all open images
		/// </summary>
		/// <param name="sender">Not Used</param>
		/// <param name="e">Not Used</param>
		private void saveAllImageMenuItem_Click(object sender, RoutedEventArgs e)
		{
			SaveAllImages();
		}

		/// <summary>
		/// Saves the currently selected image
		/// </summary>
		private void SaveSelectedImage()
		{
			if (HasSelectedBitmapTagger)
			{
				var oldCursor = Cursor;
				Cursor = Cursors.Wait;
				try
				{
					SelectedBitmapTagger.Save();
				}
				catch (Exception)
				{
					TaskDialog dialog = new TaskDialog();
					dialog.Caption = "Problem Saving Image";
					dialog.FootNote = "";
					TextBlock text = new TextBlock();
					text.TextWrapping = TextWrapping.WrapWithOverflow;
					text.Text = "The file: " + SelectedBitmapTagger.FilePath + " could not be saved..";
					TextBlock goToText = new TextBlock();
					goToText.TextWrapping = TextWrapping.WrapWithOverflow;
					goToText.Text = "Please try saving this image again. If this problem occurrs again please report the issue at the following website.";

					TextBlock textLink = (TextBlock)CloneUsingXaml((TextBlock)FindResource("issueLink"));
					StackPanel stack = new StackPanel();
					stack.Children.Add(text);
					stack.Children.Add(goToText);
					stack.Children.Add(textLink);
					dialog.Message = stack;
					dialog.Title = "File Cannot be Saved";
					dialog.FootNote = "File Cannot be Saved";
					dialog.Height = 200;
					if(!startingApplication)
						dialog.Owner = this;
					dialog.ShowDialog();
				}
				Cursor = oldCursor;
			}
		}

		/// <summary>
		/// Saves all open images
		/// </summary>
		private void SaveAllImages()
		{
			if (HasSelectedBitmapTagger)
			{
				var oldCursor = Cursor;
				Cursor = Cursors.Wait;
				foreach (ObservableBitmapTagger tagger in bitmapTaggers)
				{
					try
					{
						tagger.Save(Settings.Default.SaveExif, 
							Settings.Default.SaveXMPExif, 
							Settings.Default.SaveXMP);
					}
					catch (Exception)
					{
						TaskDialog dialog = new TaskDialog();
						dialog.Caption = "Problem Saving Image";
						dialog.FootNote = "";
						TextBlock text = new TextBlock();
						text.TextWrapping = TextWrapping.WrapWithOverflow;
						text.Text = "The file: " + tagger.FilePath + " could not be saved..";
						TextBlock goToText = new TextBlock();
						goToText.TextWrapping = TextWrapping.WrapWithOverflow;
						goToText.Text = "Please try saving this image again. If this problem occurrs again please report the issue at the following website.";

						TextBlock textLink = (TextBlock)CloneUsingXaml((TextBlock)FindResource("issueLink"));
						StackPanel stack = new StackPanel();
						stack.Children.Add(text);
						stack.Children.Add(goToText);
						stack.Children.Add(textLink);
						dialog.Message = stack;
						dialog.Title = "File Cannot be Saved";
						dialog.FootNote = "File Cannot be Saved";
						dialog.Height = 200;
						if(!startingApplication)
							dialog.Owner = this;
						dialog.ShowDialog();
					}
				}
				Cursor = oldCursor;
			}
		}

		/// <summary>
		/// Provides the user with a dialog to select a new save file and 
		/// saves the currently selected image to the new user selected save path
		/// </summary>
		private void SaveAsSelectedImage()
		{
			if (HasSelectedBitmapTagger)
			{
				SaveFileDialog saveFileDialog = new SaveFileDialog();
				saveFileDialog.Filter = "JPEG (*.jpg;*.jpeg)|*.jpg;*.jpeg|PNG (*.png)|*.png|All Files (*.*)|*.*";
				string extension = System.IO.Path.GetExtension(SelectedBitmapTagger.FilePath);
				if (ExtensionChecker.IsJPEGExtension(extension))
					saveFileDialog.FilterIndex = 1;
				else if (ExtensionChecker.IsPNGExtension(extension))
					saveFileDialog.FilterIndex = 2;
				

				var oldCursor = Cursor;
				Cursor = Cursors.Wait;
				if ((bool)saveFileDialog.ShowDialog())
				{
					try
					{
						SelectedBitmapTagger.Save(saveFileDialog.FileName, 
							Settings.Default.SaveExif, 
							Settings.Default.SaveXMPExif, 
							Settings.Default.SaveXMP);
						imageTagView.UpdateFilePath();
					}
					catch (Exception)
					{
						TaskDialog dialog = new TaskDialog();
						dialog.Caption = "Problem Saving Image";
						dialog.FootNote = "";
						TextBlock text = new TextBlock();
						text.TextWrapping = TextWrapping.WrapWithOverflow;
						text.Text = "The file: " + SelectedBitmapTagger.FilePath + " could not be saved..";
						TextBlock goToText = new TextBlock();
						goToText.TextWrapping = TextWrapping.WrapWithOverflow;
						goToText.Text = "Please try saving this image again. If this problem occurrs again please report the issue at the following website.";

						TextBlock textLink = (TextBlock)CloneUsingXaml((TextBlock)FindResource("issueLink"));
						StackPanel stack = new StackPanel();
						stack.Children.Add(text);
						stack.Children.Add(goToText);
						stack.Children.Add(textLink);
						dialog.Message = stack;
						dialog.Title = "File Cannot be Saved";
						dialog.FootNote = "File Cannot be Saved";
						dialog.Height = 200;
						if(!startingApplication)
							dialog.Owner = this;
						dialog.ShowDialog();
					}
				}

				Cursor = oldCursor;
			}
			
		}

		#endregion


		#region change selected index 

		/// <summary>
		/// Listens for when the forward button is clicked
		/// </summary>
		/// <param name="sender">Not Used</param>
		/// <param name="e">Not Used</param>
		private void forwardButton_Click(object sender, RoutedEventArgs e)
		{
			IncreaseSelectedIndex(true);
		}

		/// <summary>
		/// Listens for when the back button is clicked
		/// </summary>
		/// <param name="sender">Not Used</param>
		/// <param name="e">Not Used</param>
		private void backButton_Click(object sender, RoutedEventArgs e)
		{
			DecreaseSelectedIndex(true);
		}

		/// <summary>
		/// Increases the SelectedIndex if possible
		/// </summary>
		/// <param name="showImageAfterIncrease">Whether or not you'd like the 
		/// image to be shown after the index has increased</param>
		private void IncreaseSelectedIndex(bool showImageAfterIncrease)
		{
			if (SelectedIndex < bitmapTaggers.Count - 1)
			{
				var oldIndex = SelectedIndex;
				SelectedIndex++;
				if (oldIndex != SelectedIndex && showImageAfterIncrease)
					imageTagView.ShowImage();

			}
		}

		/// <summary>
		/// Decreases the SelectedIndex if possible
		/// </summary>
		/// <param name="showImageAfterDecrease">Whether or not you'd like the 
		/// image to be shown after the index has decreased</param>
		private void DecreaseSelectedIndex(bool showImageAfterDecrease)
		{
			if (SelectedIndex > 0)
			{
				var oldIndex = SelectedIndex;
				SelectedIndex--;
				if (oldIndex != SelectedIndex && showImageAfterDecrease)
					imageTagView.ShowImage();
			}
		}


		/// <summary>
		/// Listens for when the mousewheel is scrolled and changes the selected index
		/// </summary>
		/// <param name="sender">Not Used</param>
		/// <param name="e">The event args from the mouse wheel event</param>
		private void mainWindow_MouseWheel(object sender, MouseWheelEventArgs e)
		{
			if (e.Delta > 0)
				IncreaseSelectedIndex(true);
			if (e.Delta < 0)
				DecreaseSelectedIndex(true);
		}

		#endregion

		#region close

		/// <summary>
		/// Closes the selected image if there is one.
		/// </summary>
		private void CloseSelectededImage()
		{
			
			if (HasSelectedBitmapTagger)
			{
				//Need to save old values to compensate for the OnSelectedIndex
				//method from firing when DecreaseSelectedIndex sometimes changes
				//the selected index.
				var oldIndex = SelectedIndex;
				DecreaseSelectedIndex(true);

				bitmapTaggers.RemoveAt(oldIndex);


				if (oldIndex == SelectedIndex)
				{
					ShowSelectedBitmapTagger();
				}
				
				SetForwardButtonVisibility();
				SetBackButtonVisibility();
				SetImageCollectionControlGridVisibility();

			}
		}

		/// <summary>
		/// Listens for when closeCurrentImageMenuItem is clicked and closes
		/// the selected image.
		/// </summary>
		/// <param name="sender">Not Used</param>
		/// <param name="e">Not Used</param>
		private void closeCurrentImageMenutItem_Click(object sender, RoutedEventArgs e)
		{
			CloseSelectededImage();
		}

		/// <summary>
		/// Exits the ImageTagEditor application
		/// </summary>
		private void ExitApplication()
		{
			Application.Current.Shutdown();
		}

		/// <summary>
		/// Listens for when exitMenutItem is clicked and exits the 
		/// application
		/// </summary>
		/// <param name="sender">Not Used</param>
		/// <param name="e">Not Used</param>
		private void exitMenuItem_Click(object sender, RoutedEventArgs e)
		{
			ExitApplication();
		}

		#endregion

		#region open 

		/// <summary>
		/// Provides the user with a dialog to open an image, and will open
		/// the user selected image
		/// </summary>
		private void OpenImage()
		{
			OpenFileDialog openDialog = new OpenFileDialog();
			openDialog.DefaultExt = "*.jpg";
			openDialog.Filter = "Images (*.jpg;*.jpeg *.png)|*.jpg;*.jpeg;*.png|JPEG (*.jpg;*.jpeg)|*.jpg;*.jpeg|PNG (*.png)|*.png|All Files (*.*)|*.*";
			openDialog.FilterIndex = 1;

			var oldCursor = Cursor;
			Cursor = Cursors.Wait;
			if ((bool)openDialog.ShowDialog())
				OpenImage(openDialog.FileName, true);

			if (HasSelectedBitmapTagger)
				imageTagView.ShowImage();
			Cursor = oldCursor;

			
			
		}

		/// <summary>
		/// Opens the passed image file, create a new view for it and 
		/// adds it to the image view collection
		/// </summary>
		/// <remarks>
		/// If there were not images in the image view collection before
		/// the opening, the newly opened image will be set as the selected index
		/// If you want to the image to be displayed call ShowImage on imageTagView
		/// </remarks>
		/// <param name="filePath">The full path to the image</param>
		/// <param name="setAsSelectedImage">Whether or not to set the image as the
		/// selected image</param>
		private void OpenImage(string filePath, bool setAsSelectedImage)
		{
			string extension = System.IO.Path.GetExtension(filePath);
			if (!ExtensionChecker.IsJPEGExtension(extension) &&
				!ExtensionChecker.IsPNGExtension(extension))
			{
				TaskDialog dialog = new TaskDialog();
				dialog.Caption = "Image Type Not Supported";
				dialog.FootNote = "Only JPEG and PNG are supported at this time.";
				dialog.Message = "Image: " + filePath + " is not a currently supported format.";
				dialog.Title = "Image Type Not Supported";
				dialog.Height = 120;
				if(!startingApplication)
					dialog.Owner = this;
				dialog.ShowDialog();
				return;
			}
			if (bitmapTaggers.Any(tagger => tagger.FilePath.Equals(filePath)))
			{
				if (!startingApplication)
				{
					//Don't show this pop up during the initial start up, quitely disregard
					//duplicates instead
					TaskDialog dialog = new TaskDialog();
					dialog.Caption = "Image Already Loaded";
					dialog.FootNote = "Cannot load same image twice";
					dialog.Message = "Image: " + filePath + " is already loaded.";
					dialog.Title = "Image Already Loaded";
					dialog.Height = 120;
					dialog.Owner = this;
					dialog.ShowDialog();
				}
				return;
			}

			if(!System.IO.File.Exists(filePath))
			{
				if (!startingApplication)
				{
					//Don't show this pop up during the initial start up, quitely disregard
					//duplicates instead
					TaskDialog dialog = new TaskDialog();
					dialog.Caption = "Image Does Not Exist";
					dialog.FootNote = "";
					dialog.Message = "The file: " + filePath + " does not exist.";
					dialog.Title = "Image Does Not Exist";
					dialog.Height = 120;
					dialog.Owner = this;
					dialog.ShowDialog();
				}
				return;
			}
			try
			{
				var oldCount = bitmapTaggers.Count;
				ObservableBitmapTagger tagger = new ObservableBitmapTagger(
					BitmapTagger.BitmapTagger.Create(filePath,
					Settings.Default.LoadExif, Settings.Default.LoadXMPExif,
					Settings.Default.LoadXMP, Settings.Default.TagPrecedence));
				bitmapTaggers.Add(tagger);


				imageTagView.Tagger = tagger;
				if (setAsSelectedImage)
					SelectedIndex = LargestIndex;

				//To compensate for the situation where there were no images opened before
				//this to get around a limitation of dependency propreties
				if (oldCount == 0 && setAsSelectedImage)
					ShowSelectedBitmapTagger();

			}
			catch (Exception)
			{
				TaskDialog dialog = new TaskDialog();
				dialog.Caption = "Problem Loading Image";
				dialog.FootNote = "";
				TextBlock text = new TextBlock();
				text.TextWrapping = TextWrapping.WrapWithOverflow;
				text.Text = "The file: " + filePath + " could not be openend, it may not be a supported type or could be corrupted.";
				TextBlock goToText = new TextBlock();
				goToText.TextWrapping = TextWrapping.WrapWithOverflow;
				goToText.Text = "If you believe that this image is not corrupted and an error has occcured please report this problem at the following site.";

				TextBlock textLink = (TextBlock)CloneUsingXaml((TextBlock)FindResource("issueLink"));
				StackPanel stack = new StackPanel();
				stack.Children.Add(text);
				stack.Children.Add(goToText);
				stack.Children.Add(textLink);
				dialog.Message = stack;
				dialog.Title = "File Cannot be Opened";
				dialog.FootNote = "File Cannot be Opened";
				dialog.Height = 200;
				if(!startingApplication)
					dialog.Owner = this;
				dialog.ShowDialog();
			}
		}


		/// <summary>
		/// Opens all images in a directory, the user selects a folder
		/// from a dialog
		/// </summary>
		/// 
		private void OpenDirectoryOfImages()
		{
			var oldCursor = Cursor;
			Cursor = Cursors.Wait;
			System.Windows.Forms.FolderBrowserDialog folderDialog =
				new System.Windows.Forms.FolderBrowserDialog();
			if (folderDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
			{
				
				string directory = folderDialog.SelectedPath;


				string[] fileArray = System.IO.Directory.GetFiles(directory);
				List<string> imageFiles = fileArray.ToList<string>();
				imageFiles.RemoveAll((string file) =>
					{
						string extension = System.IO.Path.GetExtension(file);
						if (ExtensionChecker.IsJPEGExtension(extension) ||
							ExtensionChecker.IsPNGExtension(extension))
							return false;
						return true;
					});

				foreach (string file in imageFiles)
				{
					OpenImage(file, true);
				}
				
			}
			if (HasSelectedBitmapTagger)
				imageTagView.ShowImage();
			Cursor = oldCursor;

			
		}

		/// <summary>
		/// Opens all images in a directory
		/// </summary>
		/// <param name="directory">The directory to open</param>
		/// <remarks>
		/// Call ShowImage on imageTageView is this is the last opening you are doing
		/// </remarks>
		private void OpenDirectoryOfImages(string directory)
		{
			Debug.Assert(IsDirectory(directory), 
				"OpenDirectoryOfImages must be passed only directory paths.");

			string[] fileArray = System.IO.Directory.GetFiles(directory);
			List<string> imageFiles = fileArray.ToList<string>();
			imageFiles.RemoveAll((string file) =>
			{
				string extension = System.IO.Path.GetExtension(file);
				if (ExtensionChecker.IsJPEGExtension(extension) ||
					ExtensionChecker.IsPNGExtension(extension))
					return false;
				return true;
			});

			foreach (string file in imageFiles)
			{
				OpenImage(file, true);
			}
		}

		

		/// <summary>
		/// Listens for when openImageMenuItem is clicked and opens an image
		/// </summary>
		/// <param name="sender">Not Used</param>
		/// <param name="e">Not Used</param>
		private void openImageMenuItem_Click(object sender, RoutedEventArgs e)
		{
			OpenImage();
		}

		

		/// <summary>
		/// Listens for when openImageDirectoryMenuItem is clicked and opens a 
		/// directory of images
		/// </summary>
		/// <param name="sender">Not Used</param>
		/// <param name="e">Not Used</param>
		private void openImageDirectoryMenuItem_Click(object sender, RoutedEventArgs e)
		{
			OpenDirectoryOfImages();
		}

		#endregion

		#region user settings

		/// <summary>
		/// Listens for when the main window is closing and saves the user's settings
		/// </summary>
		/// <param name="sender">Not Used</param>
		/// <param name="e">Not Used</param>
		private void mainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
		{
			SaveSettings();
		}

		/// <summary>
		/// Saves the current user's settings
		/// </summary>
		private void SaveSettings()
		{
			Settings.Default.WindowHeight = Height;
			Settings.Default.WindowWidth = Width;

			Settings.Default.IsGeneralExpanded = imageTagView.IsGeneralExpanded;
			Settings.Default.IsDescriptoinExpanded = imageTagView.IsDescriptionExpanded;
			Settings.Default.IsDateTimeExpanded = imageTagView.IsDateTimeExpanded;
			Settings.Default.IsPictureConditionsExpanded = imageTagView.IsPictureConditionsExpanded;

			//Remember all the currently open images
			System.Collections.Specialized.StringCollection fileCollection = new System.Collections.Specialized.StringCollection();
			foreach (ObservableBitmapTagger tagger in bitmapTaggers)
				fileCollection.Add(tagger.FilePath);

			Settings.Default.OpenImages = fileCollection;

			Settings.Default.UserCommentVisibility = (Visibility)FindResource("userCommentVisibility");
			Settings.Default.MakerNoteVisibility = (Visibility)FindResource("makerNoteVisibility");

			Settings.Default.Save();
		}

		/// <summary>
		/// Loads the user's settings
		/// </summary>
		private void LoadSettings()
		{
			Height = Settings.Default.WindowHeight;
			Width = Settings.Default.WindowWidth;
			imageTagView.IsGeneralExpanded = Settings.Default.IsGeneralExpanded;
			imageTagView.IsDescriptionExpanded = Settings.Default.IsDescriptoinExpanded;
			imageTagView.IsDateTimeExpanded = Settings.Default.IsDateTimeExpanded;
			imageTagView.IsPictureConditionsExpanded = Settings.Default.IsPictureConditionsExpanded;

			//Load the previously opened filecollection
			var fileCollection = Settings.Default.OpenImages;
			//Only open files if any exist
			if (fileCollection != null)
			{
				foreach (string file in fileCollection)
				{
					//Make sure the file is still there
					if (System.IO.File.Exists(file))
						OpenImage(file, true);
				}
			}

			Application.Current.Resources["userCommentVisibility"] = Settings.Default.UserCommentVisibility;
			Application.Current.Resources["makerNoteVisibility"] = Settings.Default.MakerNoteVisibility;

		}

		#endregion

		#region keyboard shortcuts

		/// <summary>
		/// Listens for possible keyboard shortcuts
		/// </summary>
		/// <param name="sender">Not Used</param>
		/// <param name="e">The event arguments from the key down event</param>
		private void mainWindow_PreviewKeyDown(object sender, KeyEventArgs e)
		{
			if (e.KeyboardDevice.Modifiers == ModifierKeys.Control)
			{
				//Ctrl + S = Save current Image
				if (e.Key == Key.S)
				{
					e.Handled = true;
					SaveSelectedImage();
				}
				//Ctrl + O = Open Image
				else if(e.Key == Key.O)
				{
					e.Handled = true;
					OpenImage();
				}
				//Ctrl + F = Open Folder of Image
				else if(e.Key == Key.F)
				{
					e.Handled = true;
					OpenDirectoryOfImages();
				}
				//Ctrl + W = close Current Image
				else if(e.Key == Key.W)
				{
					e.Handled = true;
					CloseSelectededImage();
				}
				//Ctrl + RightArrow or Ctrl + Up arrow = increase selected index
				else if (e.Key == Key.Right || e.Key == Key.Up)
				{
					IncreaseSelectedIndex(true);
				}
				//Ctrl + LeftArrow or Ctrl + Down arrow = decrease selected index
				else if (e.Key == Key.Left || e.Key == Key.Down)
				{
					DecreaseSelectedIndex(true);
				}
			}
			else if (e.KeyboardDevice.Modifiers == (ModifierKeys.Control | ModifierKeys.Shift))
			{
				//Ctrl + Shift + S = Save All
				if(e.Key == Key.S)
				{
					e.Handled = true;
					SaveAllImages();
				}
			}
			else if(e.KeyboardDevice.Modifiers == (ModifierKeys.Control | ModifierKeys.Alt))
			{
				//Ctrol + Alt + S = Save Current Image As
				if(e.Key == Key.S)
				{
					e.Handled = true;
					SaveAsSelectedImage();
				}
			}
		}

		#endregion


		#region drag and drop

		/// <summary>
		/// Listens for when an item is dragged into the window, provides visual
		/// cues that it can accept the drag
		/// </summary>
		/// <param name="sender">Not used</param>
		/// <param name="e">Arguments about the dragged item</param>
		private void mainWindow_DragEnter(object sender, DragEventArgs e)
		{
			bool validDrop = false;
			if (e.Data.GetDataPresent(DataFormats.FileDrop))
			{
				string[] draggedFiles = e.Data.GetData(DataFormats.FileDrop) as string[];
				if (draggedFiles != null)
				{
					if (draggedFiles.Any<string>(file => IsValidDropFile(file)))
						validDrop = true;
				}
			}


			if (validDrop)
			{
				e.Effects = DragDropEffects.Copy;
				Effect = dragEffect;
			}
			else
				e.Effects = DragDropEffects.None;
			
		}



		/// <summary>
		/// Listens for when an item being dragged leaves the window and provides
		/// the visual cues.
		/// </summary>
		/// <param name="sender">Not used</param>
		/// <param name="e">Arguments about the dragged item</param>
		private void mainWindow_DragLeave(object sender, DragEventArgs e)
		{
			Effect = null;
		}


		/// <summary>
		/// Listens for when an item is dropped into the window and calls opens
		/// supported file
		/// </summary>
		/// <param name="sender">Not used</param>
		/// <param name="e">The arguments for a drag drop event</param>
		private void mainWindow_Drop(object sender, DragEventArgs e)
		{
			Effect = null;
			if (e.Data.GetDataPresent(DataFormats.FileDrop))
			{
				string[] droppedFiles = e.Data.GetData(DataFormats.FileDrop) as string[];
				if (droppedFiles != null)
				{
					var oldCursor = Cursor;
					Cursor = Cursors.Wait;
					foreach (string file in droppedFiles)
					{
						if(IsValidDropFile(file))
						{
							if (IsDirectory(file))
								OpenDirectoryOfImages(file);
							else
								OpenImage(file, true);
						}
					}

					if (HasSelectedBitmapTagger)
						imageTagView.ShowImage();
					Cursor = oldCursor;
				}
			}
		}

		/// <summary>
		/// Get whether or not a file is a supported file type.
		/// </summary>
		/// <param name="file">The file to check</param>
		/// <returns>True if it is supported, false if not</returns>
		private bool IsValidDropFile(string file)
		{
			string extension = System.IO.Path.GetExtension(file);
			return IsDirectory(file) || 
				ExtensionChecker.IsJPEGExtension(extension) ||
				ExtensionChecker.IsPNGExtension(extension);
		}

		/// <summary>
		/// Tell if the passed path is a directory or file
		/// </summary>
		/// <param name="path">The path to test</param>
		/// <returns>True if it is a directory, false if not</returns>
		private bool IsDirectory(string path)
		{
			// get the file attributes for file or directory
			FileAttributes attr = File.GetAttributes(path);

			//detect whether its a directory or file
			if ((attr & FileAttributes.Directory) == FileAttributes.Directory)
				return true;

			return false;
		}

		#endregion

		/// <summary>
		/// Listens for when the options menu item is clicked and shows
		/// the image tag options dialog box
		/// </summary>
		/// <param name="sender">Not Used</param>
		/// <param name="e">Not Used</param>
		private void optionsMenuItem_Click(object sender, RoutedEventArgs e)
		{
			ImageTagOptionsDialog dialog = new ImageTagOptionsDialog();

			dialog.SaveExif = Settings.Default.SaveExif;
			dialog.SaveXMPExif = Settings.Default.SaveXMPExif;
			dialog.SaveXMP = Settings.Default.SaveXMP;

			dialog.LoadXMP = Settings.Default.LoadXMP;
			dialog.LoadXMPExif = Settings.Default.LoadXMPExif;
			dialog.LoadExif = Settings.Default.LoadExif;

			var userCommentVisibility = (Visibility)FindResource("userCommentVisibility");
			var makerNoteVisibility = (Visibility)FindResource("makerNoteVisibility");

			if (userCommentVisibility == Visibility.Visible)
				dialog.ShowUserComment = true;
			else
				dialog.ShowUserComment = false;

			if (makerNoteVisibility == Visibility.Visible)
				dialog.ShowMakerNote = true;
			else
				dialog.ShowMakerNote = false;


			dialog.Precedence = Settings.Default.TagPrecedence;

			dialog.Owner = this;

			if ((bool)dialog.ShowDialog())
			{
				Settings.Default.SaveExif = dialog.SaveExif;
				Settings.Default.SaveXMPExif = dialog.SaveXMPExif;
				Settings.Default.SaveXMP = dialog.SaveXMP;

				Settings.Default.LoadXMP = dialog.LoadXMP;
				Settings.Default.LoadXMPExif = dialog.LoadXMPExif;
				Settings.Default.LoadExif = dialog.LoadExif;

				Settings.Default.TagPrecedence = dialog.Precedence; 

				if(dialog.ShowMakerNote)
					Application.Current.Resources["makerNoteVisibility"] = Visibility.Visible;
				else
					Application.Current.Resources["makerNoteVisibility"] = Visibility.Collapsed;

				if (dialog.ShowUserComment)
					Application.Current.Resources["userCommentVisibility"] = Visibility.Visible;
				else
					Application.Current.Resources["userCommentVisibility"] = Visibility.Collapsed;
			}
		}



		/// <summary>
		/// Workaround for cloning a WPF control.
		/// </summary>
		/// <param name="o">The WPF control to clone</param>
		/// <returns>The clone of the passed control</returns>
		private FrameworkElement CloneUsingXaml(FrameworkElement o)
		{
			string xaml = XamlWriter.Save(o);
			return (FrameworkElement)XamlReader.Load(new XmlTextReader(new StringReader(xaml)));
		}

	}
}
